spring cloud alibaba gateway 整合 jwt 实现鉴权 您所在的位置:网站首页 spring security websocket 鉴权 spring cloud alibaba gateway 整合 jwt 实现鉴权

spring cloud alibaba gateway 整合 jwt 实现鉴权

2023-10-31 23:07| 来源: 网络整理| 查看: 265

最近在搭建阿里巴巴的微服务框架,这次是引入jwt实现鉴权,主要包括以下功能

(1)登录。接收用户名,密码,校验密码是否正确,若正确则返回token(jwt生成),若错误返回提示信息。

(2)请求网关时校验token。

(3)登出。接收token,将指定token置为失效的状态。

(4)续签。对前端服务部署服务器发过来的请求对过期的token直接返回新的token,并提示更换token。

功能实现涉及两个服务,网关服务,鉴权服务。

 

一、在鉴权服务中

pom关键配置:

0.11.5 org.springframework.boot spring-boot-starter-web com.auth0 java-jwt 3.10.0 io.jsonwebtoken jjwt-api ${jjwt.version} io.jsonwebtoken jjwt-impl ${jjwt.version} runtime io.jsonwebtoken jjwt-jackson ${jjwt.version} runtime org.springframework.boot spring-boot-starter-data-redis joda-time joda-time 2.9.6

 

配置文件关键配置:

auth: expireMinutes: 20 #过期分钟数 key: 0123456789_0123456789_0123456789 #token生成

#省略网关,redis等其他常规设置

 

 工具类(关键代码)

package com.example.auth.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.example.auth.constant.JWTConstants; import com.example.auth.model.AuthUser; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Map; /** * jwt工具类 */ @Component public class JwtUtils { @Value("${auth.expireMinutes:30}") private int expireMinutes; /** * 生成密钥 * * @return 生成密钥 */ Algorithm getAlgorithm() { return Algorithm.HMAC256(JWTConstants.JWT_KEY); } /** * 生成token * * @param userId 用户id * @param userName 用户名 * @param userRole 用户的角色 * @return token jwtToken */ public String generateToken(String userId, String userName, String userRole) { return JWT.create() .withClaim(JWTConstants.JWT_KEY_USER_NAME, userName) .withClaim(JWTConstants.JWT_KEY_ROLE, userRole) .withClaim(JWTConstants.JWT_KEY_ID, userId) .withExpiresAt(DateTime.now().plusSeconds(expireMinutes).toDate()) .sign(getAlgorithm()); } /** * 根据用户信息生成用户 * * @param user 用户信息 * @return token jwtT oken */ public String generateToken(AuthUser user) { return generateToken(user.getId(), user.getUsername(), user.getRole()); } /** * 解码token * * @param token jwtToken * @return 用户信息 */ public AuthUser decode(String token) { AuthUser authUser = new AuthUser(); DecodedJWT decodedJWT = JWT.require(getAlgorithm()).build().verify(token); Map jwt = decodedJWT.getClaims(); String userName = jwt.get(JWTConstants.JWT_KEY_USER_NAME).asString(); String userId = jwt.get(JWTConstants.JWT_KEY_ID).asString(); authUser.setId(userId); authUser.setUsername(userName); return authUser; } }

 

登录接口

package com.example.auth.controller; import com.alibaba.nacos.common.model.RestResult; import com.example.auth.model.AuthUser; import com.example.auth.model.LoginReturn; import com.example.auth.utils.JwtUtils; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("auth") @RefreshScope public class AuthController { @Resource JwtUtils jwtUtils; @PostMapping("/login") public RestResult login(@RequestBody AuthUser user) { //模拟数据库查询的用户 AuthUser tagetUser = new AuthUser(); tagetUser.setPassword("123456"); //密码校验(demo密码未加密) if (!tagetUser.getPassword().equals(user.getPassword())) { return new RestResult(-1, "用户名与密码不正确"); } String token = jwtUtils.generateToken(user); return new RestResult(1, "认证成功", new LoginReturn(user,token)); } }

 

 其他实体类

package com.example.auth.model; import lombok.*; /** * @author songyan */ @Data @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class AuthUser { /** * 主键 */ private String id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 角色 */ private String role; } AuthUser LoginReturn package com.example.auth.constant; public class JWTConstants { public static final String JWT_REQUEST_HEADER_KEY = "Authorization"; public static final String JWT_KEY = "0123456789_0123456789_0123456789"; public static final String JWT_KEY_ID = "user_id"; public static final String JWT_KEY_USER_NAME = "user_name"; public static final String JWT_KEY_ROLE = "user_role"; public static final String JWT_REQUEST_KEY_ID = JWT_KEY_ID; public static final String JWT_REQUEST_KEY_USER_NAME = JWT_KEY_USER_NAME; public static final String JWT_REQUEST_KEY_ROLE = JWT_KEY_ROLE; } JWTConstants package com.example.auth.configuration; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfiguration { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } } RedisConfiguration package com.example.auth.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.Collection; import java.util.concurrent.TimeUnit; @Component public class RedisUtil { @Autowired private RedisTemplate redisTemplate; /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } } RedisUtil

二、网关服务

依赖

com.example auth 0.0.1-SNAPSHOT compile org.springframework.boot spring-boot-starter-web

 

JwtTokenFilter  全局过滤器 package com.luoxun.gateway.filter; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.common.model.RestResult; import com.auth0.jwt.exceptions.TokenExpiredException; import com.example.auth.constant.JWTConstants; import com.example.auth.model.AuthUser; import com.example.auth.utils.JwtUtils; import com.example.auth.utils.RedisUtil; import com.luoxun.gateway.constant.AuthConstant; import com.luoxun.gateway.constant.AuthReturnMessage; import com.luoxun.gateway.constant.BEANOrder; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; /** * JwtToken 过滤器 */ @Order(BEANOrder.JWT_TOKEN_FILTER) @Component @Slf4j public class JwtTokenFilter implements GlobalFilter { @Value("${auth.tt}") private String skipAuthUrls; @Value("${auth.overduceTime}") private int overdueTime; @Value("${auth.webIp}") private String webIp; @Resource private JwtUtils jwtUtils; @Resource private RedisUtil redisUtil; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String url = exchange.getRequest().getURI().getPath(); //跳过不需要验证的路径 if (skipAuthUrls != null && url.equals(skipAuthUrls)) { //继续路由 return chain.filter(exchange); } //获取token String token = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_HEADER_KEY); ServerHttpResponse resp = exchange.getResponse(); if (StringUtils.isBlank(token)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_NULL); } else { try { //校验token并解析token AuthUser loginUser = jwtUtils. decode(token); //登出 if (url.equals(AuthConstant.URL_LOGOUT)) { redisUtil.set(AuthConstant.PREFIX_TOKEN_LAPSED + loginUser.getUsername(), token, overdueTime); return authSuccess(resp, AuthReturnMessage.AUTH_SUCCESS_LOGOUT); } //判断token是否已弃用 String sig = redisUtil.get(AuthConstant.PREFIX_TOKEN_LAPSED + loginUser.getUsername()) + ""; if (token.equals(sig)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_LAPSED); } //继续路由 return chain.filter(exchange); } catch (TokenExpiredException tokenExpiredException) { //处理过期的token return expiredToken(tokenExpiredException, exchange, url, token); } catch (Exception e) { log.error(e.getMessage(), e); return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_FAIL); } } } /** * token过期的处理 * * @param tokenExpiredException * @param exchange * @param url * @param token * @return */ private Mono expiredToken(TokenExpiredException tokenExpiredException, ServerWebExchange exchange, String url, String token) { log.error(tokenExpiredException.getMessage(), tokenExpiredException); String userName = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_USER_NAME); String userId = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_ID); String userRole = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_ROLE); ServerHttpRequest request = exchange.getRequest(); String ip = request.getURI().getHost(); ServerHttpResponse resp = exchange.getResponse(); if (webIp.equals(ip)) { //登出 if (url.equals(AuthConstant.URL_LOGOUT)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } //判断token是否已弃用 String sig = redisUtil.get(AuthConstant.PREFIX_TOKEN_LAPSED + userName) + ""; if (token.equals(sig)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } //续签 String newToken = jwtUtils.generateToken(userId, userName, userRole); return authSuccess(resp, new RestResult(HttpStatus.OK.value(), AuthReturnMessage.AUTH_SUCCESS_TIP_REFRESH, newToken)); } return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } /** * 认证错误输出 * * @param resp 响应对象 * @param mess 错误信息 * @return */ private Mono authError(ServerHttpResponse resp, String mess) { resp.setStatusCode(HttpStatus.UNAUTHORIZED); resp.getHeaders().add("Content-Type", "application/json;"); DataBuffer buffer = resp.bufferFactory().wrap(mess.getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } private Mono authSuccess(ServerHttpResponse resp, RestResult mess) { resp.setStatusCode(HttpStatus.OK); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer buffer = resp.bufferFactory().wrap(JSONObject.toJSONString(mess).getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } private Mono authSuccess(ServerHttpResponse resp, String mess) { resp.setStatusCode(HttpStatus.OK); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer buffer = resp.bufferFactory().wrap(mess.getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } }

 

其他

package com.luoxun.gateway.configuration; import com.example.auth.utils.JwtUtils; import com.example.auth.utils.RedisUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CommonConfiguration { /** * 注入jwt工具类 * * @return jwt工具类 */ @Bean public JwtUtils jwtUtils() { return new JwtUtils(); } @Bean public RedisUtil redisUtil() { return new RedisUtil(); } } CommonConfiguration package com.luoxun.gateway.configuration; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfiguration { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } } RedisConfiguration package com.luoxun.gateway.constant; public class AuthConstant { public final static String PREFIX_TOKEN_LAPSED = "token_lapsed_"; public final static String URL_LOGOUT = "/auth/loginOut"; } AuthConstant package com.luoxun.gateway.constant; public class AuthReturnMessage { public final static String AUTH_EXCEPTION_FAIL = "认证失败"; public final static String AUTH_EXCEPTION_LAPSED = "认证失效"; public final static String AUTH_EXCEPTION_EXPIRED = "认证过期"; public final static String AUTH_EXCEPTION_NULL = "认证信息不可为空"; public final static String AUTH_SUCCESS_LOGOUT = "已登出"; public final static String AUTH_SUCCESS_TIP_REFRESH = "请更换新token"; } AuthReturnMessage package com.luoxun.gateway.constant; public class BEANOrder { public static final int JWT_TOKEN_FILTER = -100; } BEANOrder

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有